Unit testing an analyzer
To help analyzer authors testing their analyzers, there's a dedicated testing package. It contains easy to use utility functions to create a context for the analyzer and assertion helpers.
FSharp.Analyzers.SDK.Testing.mkOptionsFromProject creates the FSharpProjectOptions for the given framework (e.g. net7.0) and the given list of packages to reference.
FSharp.Analyzers.SDK.Testing.getContext then takes the FSharpProjectOptions and the source code to test and creates a CliContext you can pass along to your analyzer function.
The module FSharp.Analyzers.SDK.Testing.Assert offers various functions to help with assertion statements from your favorite unit testing framework.
For a complete example of an unit testing project, take a look at OptionAnalyzer.Test in the samples folder of the SDK repository.
open FSharp.Compiler.CodeAnalysis
open FSharp.Analyzers.SDK.Testing
open OptionAnalyzer
open NUnit.Framework
let mutable projectOptions: FSharpProjectOptions = FSharpProjectOptions.zero
[<SetUp>]
let Setup () =
task {
let! opts =
mkOptionsFromProject
"net10.0"
[
// The SDK uses this in a "dotnet add package x --version y" command
// to generate the needed FSharpProjectOptions
{
Name = "Newtonsoft.Json"
Version = "13.0.3"
}
]
projectOptions <- opts
}
[<Test>]
let ``warnings are emitted`` () =
async {
let source =
"""
module M
let notUsed() =
let option : Option<int> = None
option.Value
"""
let ctx = getContext projectOptions source
let! msgs = optionValueAnalyzer ctx
Assert.IsNotEmpty msgs
Assert.IsTrue(Assert.messageContains "Option.Value" msgs[0])
}
namespace FSharp
--------------------
namespace Microsoft.FSharp
from FSharp.Analyzers.SDK
{
ProjectFileName: string
ProjectId: string option
SourceFiles: string array
OtherOptions: string array
ReferencedProjects: FSharpReferencedProject array
IsIncompleteTypeCheckEnvironment: bool
UseScriptResolutionRules: bool
LoadTime: DateTime
UnresolvedReferences: FSharpUnresolvedReferencesSet option
OriginalLoadReferences: (range * string * string) list
...
}
type SetUpAttribute =
inherit NUnitAttribute
new: unit -> unit
--------------------
SetUpAttribute() : SetUpAttribute
framework: The target framework for the tested code to use. E.g. net6.0, net7.0
additionalPkgs: A list of additional packages that should be referenced. The tested code can use these.
returns: FSharpProjectOptions
val mkOptionsFromProject:
framework: string ->
additionalPkgs: Package list ->
System.Threading.Tasks.Task<FSharpProjectOptions>
type TestAttribute =
inherit NUnitAttribute
interface ISimpleTestBuilder
interface IApplyToTest
interface IImplyFixture
new: unit -> unit
member ApplyToTest: test: Test -> unit
member BuildFrom: method: IMethodInfo * suite: Test -> TestMethod
member Author: string
member Description: string
member ExpectedResult: obj
...
--------------------
TestAttribute() : TestAttribute
opts: The project options to use.
source: The file to analyze.
returns: CliContext
val getContext:
opts: FSharpProjectOptions ->
source: string ->
FSharp.Analyzers.SDK.CliContext
ctx: FSharp.Analyzers.SDK.CliContext ->
Async<FSharp.Analyzers.SDK.Message list>
static member AreEqual: expected: float * actual: float * delta: float * message: string * args: obj array -> unit + 5 overloads
static member AreNotEqual: expected: obj * actual: obj * message: string * args: obj array -> unit + 1 overload
static member AreNotSame: expected: obj * actual: obj * message: string * args: obj array -> unit + 1 overload
static member AreSame: expected: obj * actual: obj * message: string * args: obj array -> unit + 1 overload
static member ByVal: actual: obj * expression: IResolveConstraint -> unit + 1 overload
static member Catch: code: TestDelegate * message: string * args: obj array -> exn + 5 overloads
static member CatchAsync: code: AsyncTestDelegate * message: string * args: obj array -> exn + 5 overloads
static member Contains: expected: obj * actual: ICollection * message: string * args: obj array -> unit + 1 overload
static member DoesNotThrow: code: TestDelegate * message: string * args: obj array -> unit + 1 overload
static member DoesNotThrowAsync: code: AsyncTestDelegate * message: string * args: obj array -> unit + 1 overload
...
Assert.IsNotEmpty(aString: string) : unit
Assert.IsNotEmpty(collection: System.Collections.IEnumerable, message: string, args: obj array) : unit
Assert.IsNotEmpty(aString: string, message: string, args: obj array) : unit
Assert.IsTrue(condition: System.Nullable<bool>) : unit
Assert.IsTrue(condition: bool, message: string, args: obj array) : unit
Assert.IsTrue(condition: System.Nullable<bool>, message: string, args: obj array) : unit
expectedContent: string ->
msg: FSharp.Analyzers.SDK.Message ->
bool
FSharp.Analyzers.SDK